package mgo

import (
	"context"
	"errors"
	"gitee.com/dreamwood/ez-go/db/mgo/expr"
	"gitee.com/dreamwood/ez-go/ez"
	"gitee.com/dreamwood/ez-go/ss"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"reflect"
	"time"
)

type Factory struct {
	Doc       Doc
	Ctx       context.Context
	Db        *mongo.Database
	DocConfig *DocConfig
	Session   *ez.Session

	//事件开关
	IsEventOn bool

	//是否过滤删除
	IncludeDeleted bool
}

func NewFactory(argus ...interface{}) *Factory {
	f := new(Factory)
	f.SetArgus(argus...)
	return f
}

type Doc interface {
	DocName() string
	GetId() int64
	SetId(id int64)
	ClearRelationsBeforeSave() Doc
}

func (this *Factory) SetArgus(argus ...interface{}) {
	for _, arg := range argus {
		tp := reflect.TypeOf(arg)
		switch tp.String() {
		case "context.Context":
			this.Ctx = arg.(context.Context)
		case "*mongo.Database":
			this.Db = arg.(*mongo.Database)
		case "*mgo.DocConfig":
			this.DocConfig = arg.(*DocConfig)
		case "bool":
			this.IsEventOn = arg.(bool)
		case "*ez.Session":
			this.Session = arg.(*ez.Session)
			this.Ctx = context.WithValue(context.Background(), "session", this.Session)
		default:
		}
	}
}

func (this *Factory) SetDoc(doc Doc) {
	this.Doc = doc
}

func (this *Factory) GetCollection() *mongo.Collection {
	return this.DB().Collection(this.Doc.DocName())
}

func (this *Factory) SetCtx(ctx context.Context) {
	this.Ctx = ctx
}
func (this *Factory) GetCtx() context.Context {
	return this.Ctx
}
func (this *Factory) DB() *mongo.Database {
	if this.Db == nil {
		this.Db = ez.DBMongo
	}
	return this.Db
}
func (this *Factory) SetDB(db *mongo.Database) {
	this.Db = db
}

func (this *Factory) Create(doc Doc) error {
	doc = doc.ClearRelationsBeforeSave()
	this.TriggerBeforeCreate(doc)
	if doc.GetId() == 0 {
		doc.SetId(GetNextId(doc.DocName(), this.DB()))
	}
	_, e := this.DB().Collection(doc.DocName()).InsertOne(this.Ctx, doc)
	if e != nil {
		return e
	}
	//if oid, ok := rst.InsertedID.(int64); ok {
	//	doc.SetId(oid)
	//}else {
	//	println("ID ERROR")
	//}
	this.TriggerAfterCreate(doc)
	return nil
}
func (this *Factory) Update(doc Doc, value interface{}) error {
	doc = doc.ClearRelationsBeforeSave()
	this.TriggerBeforeUpdate(doc)
	rst, e := this.DB().Collection(doc.DocName()).
		UpdateOne(this.Ctx, bson.D{{"id", doc.GetId()}}, bson.M{
			"$set": value})
	if e != nil {
		return e
	}
	this.TriggerAfterUpdate(doc)
	if rst.ModifiedCount != 1 {
		return errors.New("未更新")
	}
	return nil
}
func (this *Factory) Replace(doc Doc) error {
	doc = doc.ClearRelationsBeforeSave()
	this.TriggerBeforeUpdate(doc)
	rst, e := this.DB().Collection(doc.DocName()).ReplaceOne(this.Ctx, bson.D{{"id", doc.GetId()}}, doc)
	if e != nil {
		return e
	}
	if rst.ModifiedCount != 1 {
		return errors.New("未更新")
	}
	this.TriggerAfterUpdate(doc)
	return nil
}

func (this *Factory) Delete(doc Doc) error {
	this.TriggerDelete(doc)
	rst, e := this.DB().Collection(doc.DocName()).
		UpdateMany(this.Ctx, bson.D{{"id", doc.GetId()}}, bson.M{"$set": bson.D{
			{"deleteAt", time.Now()},
		}})
	if e != nil {
		return e
	}
	if rst.ModifiedCount != 1 {
		return errors.New("删除失败")
	}
	return nil
}

func (this *Factory) UnDelete(doc Doc) error {
	this.TriggerUnDelete(doc)
	rst, e := this.DB().Collection(doc.DocName()).
		UpdateOne(this.Ctx, bson.D{{"id", doc.GetId()}}, bson.M{"$set": bson.D{
			{"deleteAt", nil},
		}})
	if e != nil {
		return e
	}
	if rst.ModifiedCount != 1 {
		return errors.New("恢复失败")
	}
	return nil
}

func (this *Factory) Destroy(doc Doc) error {
	this.TriggerDestroy(doc)
	filter := bson.D{
		{"id", doc.GetId()},
	}
	rst, e := this.DB().Collection(doc.DocName()).DeleteOne(this.Ctx, filter)
	if e != nil {
		return e
	}
	if rst.DeletedCount != 1 {
		return errors.New("未删除任何内容")
	}
	return nil
}

func (this *Factory) FindBy(docs interface{}, query *ez.HttpQuery) error {
	if query.Page < 1 {
		query.Page = 1
	}
	if query.Limit < 1 {
		query.Limit = 0
	}
	if !this.IncludeDeleted {
		query.Conditions["deleteAt"] = nil
	}
	cursor, e := this.DB().Collection(this.Doc.DocName()).Find(
		this.Ctx,
		CreateFilters(query.Conditions),
		Option(query.Limit*query.Page-query.Limit, query.Limit, query.Order...),
	)
	if e != nil {
		return e
	}
	e = cursor.All(this.Ctx, docs)
	if e != nil {
		return e
	}
	return nil
}
func (this *Factory) Count(query *ez.HttpQuery) (int64, error) {
	if !this.IncludeDeleted {
		query.Conditions["deleteAt"] = nil
	}
	count, e := this.DB().Collection(this.Doc.DocName()).CountDocuments(
		this.Ctx,
		CreateFilters(query.Conditions),
	)
	if e != nil {
		return -1, e
	}
	return count, nil
}

func (this *Factory) FindOneBy(doc Doc, query *ez.HttpQuery) error {
	if !this.IncludeDeleted {
		query.Conditions["deleteAt"] = nil
	}
	cursor := this.DB().Collection(doc.DocName()).FindOne(
		this.Ctx,
		CreateFilters(query.Conditions),
		OptionOne(query.Order...),
	)
	e := cursor.Decode(doc)
	if e != nil {
		return e
	}
	return nil
}

func (this *Factory) FindId(doc Doc, id int64) error {
	qb := ez.NewHttpQuery()
	qb.Conditions["id"] = id
	return this.FindOneBy(doc, qb)
}

func (this *Factory) CreateSort(query *ez.HttpQuery) bson.D {
	orderBys := bson.D{}
	for _, str := range query.Order {
		if str[0:1] == "-" {
			orderBys = append(orderBys, bson.E{str[1:], -1})
		} else {
			orderBys = append(orderBys, bson.E{str, 1})
		}
	}
	return orderBys
}
func (this *Factory) CreatePipeLine(query *ez.HttpQuery) *PPL {
	ppl := NewPPL()
	filter := NewFilters()
	filter.SetDocConfig(this.DocConfig)
	filters := filter.CreateFilter(query.Conditions)
	//for _, dump := range query.Dumps {
	//	filter.PrepareJoins(dump)
	//}
	ppl.Dumps = query.Dumps
	filter.CreateJoins()
	for _, join := range filter.Joins {
		ppl.Add(join)
	}
	if len(filters) > 0 {
		ppl.AddMatch(filters)
	}
	return ppl
}

func (this *Factory) CreatePipeLineForCount(query *ez.HttpQuery) *PPL {
	ppl := NewPPL()
	filter := NewFilters()
	filter.SetDocConfig(this.DocConfig)
	filters := filter.CreateFilter(query.Conditions)
	//for _, dump := range query.Dumps {
	//	filter.PrepareJoins(dump)
	//}

	filter.CreateJoins()
	for _, join := range filter.Joins {
		ppl.Add(join)
	}
	if len(filters) > 0 {
		ppl.AddMatch(filters)
	}
	return ppl
}

func (this *Factory) AggregateFind(docs interface{}, query *ez.HttpQuery) error {
	if !this.IncludeDeleted && !query.IncludeDelete {
		query.Conditions["deleteAt"] = nil
	}
	ppl := this.CreatePipeLine(query)
	ppl.AddSort(
		this.CreateSort(query),
	)
	if query.Page < 1 {
		query.Page = 1
	}
	if query.Limit > 0 {
		ppl.AddSkip(int64(query.Limit * (query.Page - 1)))
		ppl.AddLimit(int64(query.Limit))
	}
	//重新添加需要数据挖掘的Dump
	dumpFilter := NewFilters()
	dumpFilter.SetDocConfig(this.DocConfig)
	for _, dump := range ppl.Dumps {
		dumpFilter.PrepareJoins(dump)
	}
	dumpFilter.CreateJoins()
	for _, join := range dumpFilter.Joins {
		ppl.Add(join)
	}
	ppl.AddSort(
		this.CreateSort(query),
	)
	c, e := this.DB().Collection(this.Doc.DocName()).Aggregate(this.Ctx, ppl.Get())
	if e != nil {
		return e
	}
	return c.All(this.Ctx, docs)
}

func (this *Factory) AggregateCount(query *ez.HttpQuery) (int64, error) {
	if !this.IncludeDeleted {
		query.Conditions["deleteAt"] = nil
	}
	ppl := this.CreatePipeLineForCount(query)
	ppl.Add(expr.Count("count"))
	c, e := this.DB().Collection(this.Doc.DocName()).Aggregate(this.Ctx, ppl.Get())
	if e != nil {
		return -1, e
	}
	wanted := make([]ss.Counter, 0)
	e = c.All(this.Ctx, &wanted)
	if e != nil || len(wanted) == 0 {
		return -1, e
	}
	return wanted[0].Count, nil
}
